// NAVIGATOR.cpp : Implementation of NAVIGATOR

#include "stdafx.h"
#include "NAVIGATOR.h"
#include "DataValues.h"


// NAVIGATOR

STDMETHODIMP NAVIGATOR::InterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* arr[] = 
	{
		&IID_IDMAlgorithmNavigation
	};

	for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		if (InlineIsEqualGUID(*arr[i],riid))
			return S_OK;
	}
	return S_FALSE;
}

HRESULT		NAVIGATOR::Initialize(bool bDMDimension, ALGORITHM* palgorithm)
{
	_bDMDimension	= bDMDimension;
	_palgorithm		= palgorithm;

	// Populate _viAttributeOutput with the attribute indices
	// for all of the output variables, using the attribute-set
	// interface of _palgorithm.

	ULONG	cAttribute;
	HRESULT	hr = palgorithm->_spidmattributeset->GetAttributeCount(&cAttribute);
	RETURN_ON_FAIL(hr);
	_viAttributeOutput.resize(0);
	
	for (ULONG iAttribute = 0; iAttribute < cAttribute; iAttribute++)
	{
		DM_ATTRIBUTE_FLAGS dmattributeflags;
		hr = palgorithm->_spidmattributeset->GetAttributeFlags(iAttribute, &dmattributeflags);
		RETURN_ON_FAIL(hr);

		if (dmattributeflags & DMAF_OUTPUT)
		{
			_viAttributeOutput.push_back(iAttribute);
		}
	}

	_lrphierarchy._ciAttributeOutput	= (UINT) _viAttributeOutput.size();
	_lrphierarchy._bDMDimension			= _bDMDimension;

	// Initialize state
	_iIDNode = _lrphierarchy.IIDRootFirst();
	return S_OK;
}


////
//
// IDMDAGNavigation 
//
////

STDMETHODIMP NAVIGATOR::MoveToNextTree(void)
{
	// Set the current node to be the root node of the next tree in the forest that
	// describes your model. Models are typically represented using a single tree. If 
	// the current tree is the last one, you return S_FALSE.

	ULONG iIDNodeNext = _lrphierarchy.IIDRootNext(_iIDNode);
	if( iIDNodeNext == (ULONG) -1 )
	{	
		return S_FALSE;
	}
	
	_iIDNode = iIDNodeNext;
	return S_OK;
}

	
STDMETHODIMP NAVIGATOR::GetNodeID(
	/* [out] */ DM_DAGNodeID*	out_pNodeID)
{
	// Set *out_pNodeID to be the current node id
	
	*out_pNodeID = (DM_DAGNodeID) _iIDNode;

	return S_OK;
}


STDMETHODIMP NAVIGATOR::LocateNode(
	/* [in] */ DM_DAGNodeID in_NodeID)
{
	// Set the current node to be the one indicated by in_NodeID
	_iIDNode = (ULONG)in_NodeID;
	return S_OK;
}

STDMETHODIMP NAVIGATOR::ValidateNodeID(
	/* [in] */ DM_DAGNodeID in_NodeID)
{
	// Return E_FAIL if in_NodeID does not correspond to a valid node id
	if( _lrphierarchy.BValidID( (ULONG) in_NodeID ) )
	{
		return S_OK;
	}
	else
	{
		return E_FAIL;
	}
}

STDMETHODIMP NAVIGATOR::GetParentCount(
	/* [out] */ ULONG*	out_pulParents)
{
	// Set *out_pulParents to be the number of parents of the current node
	if( _lrphierarchy.BLeaf(_iIDNode))
	{	
		*out_pulParents = 1;
	}
	else
	{
		*out_pulParents = 0;
	}

	return S_OK;
}

STDMETHODIMP NAVIGATOR::MoveToParent(
	/* [in] */ ULONG in_ParentIndex)
{
	// Change the current node to be the given parent of the 
	// current node.
	_iIDNode = _lrphierarchy.IIDParent(_iIDNode);
	return S_OK;
}


STDMETHODIMP NAVIGATOR::GetParentNodeID(
	/* [in] */ ULONG in_ParentIndex,
	/* [out] */ DM_DAGNodeID *out_pNodeID)
{
	// Set *out_pNodeID to be the node ID of the given parent
	*out_pNodeID = _lrphierarchy.IIDParent(_iIDNode);

	return S_OK;
}

STDMETHODIMP NAVIGATOR::GetChildCount(
	/* [out] */ ULONG*	out_pulChild)
{
	// Set *out_pulChild to be the number of children of the current node

	*out_pulChild = _lrphierarchy.CChild(_iIDNode);

	return S_OK;
}

STDMETHODIMP NAVIGATOR::MoveToChild(
	/* [in] */ ULONG in_ChildIndex)
{
	// Set the current node to be the given child of the current node
	_iIDNode = _lrphierarchy.IIDChild(_iIDNode, in_ChildIndex);

	return S_OK;
}

STDMETHODIMP NAVIGATOR::GetChildNodeID(
	/* [in] */ ULONG			in_ChildIndex,
	/* [out] */ DM_DAGNodeID*	out_pNodeID)
{
	// Set *out_pNodeID to be the node id of the given child of the current node
	*out_pNodeID = _lrphierarchy.IIDChild(_iIDNode, in_ChildIndex);

	return S_OK;
}

	
STDMETHODIMP NAVIGATOR::MoveToNextLeaf(
	/* [in] */ DM_DAGNodeID in_ScopeNodeID)
{
	// Set the current node to be the next leaf (in an order you decide)
	// that has node in_ScopeNodeID as an ancestor. Return S_FALSE if the
	// current node is the last such leaf.

	ULONG iIDLeaf = _lrphierarchy.IIDLeafNext(_iIDNode);
	if( iIDLeaf == (ULONG) -1 )
	{
		return S_FALSE;
	}
	_iIDNode = iIDLeaf;
	return S_OK;
}

STDMETHODIMP NAVIGATOR::AddRefNodeID(
	/* [in] */ DM_DAGNodeID in_NodeID)
{
	// Called to indicate that a reference is being made to
	// a node. You must keep any per-node resources available
	// for that reference.

	return S_OK;
}


STDMETHODIMP NAVIGATOR::ReleaseNodeID(
	/* [in] */ DM_DAGNodeID in_NodeID)
{
	// Called when a reference to the given node is released. Once
	// the ref count for a node is zero, you may release any node-specific
	// resources you allocated.

	return S_OK;
}

////
//
// IDMAlgorithmNavigation
//
////

STDMETHODIMP NAVIGATOR::GetNodeProperty(
	/* [in] */ IDMContextServices*	in_pContext,
	/* [in] */ DM_NODE_PROP			in_PropID,
	/* [in] */ DMVariantPtr			io_pValue)
{
	// Set *io_pValue to the appropriate value, depending on the property
	// desired by the caller. If the current node does not support the given
	// property, the function should return S_FALSE.
	//
	// Following are the allowed "simple" properties, the types expected for the DMVariantPtr, 
	// and a description. The "non simple" properties (DMNP_ATTRIBUTES and DMNP_DISTRIBUTION)
	// are returned using 

	// DMNP_TYPE (ULONG)		: the "type" of the current node. The set of types currently recognized
	//							  by the server are defined (as const ULONG) in "oledbdm.h" and they include:
	//
	//									DM_NODE_TYPE_MODEL        
	//									DM_NODE_TYPE_REGRESSION_TREE_ROOT         
	//									DM_NODE_TYPE_TREE_INTERIOR    
	//									DM_NODE_TYPE_TREE_DISTRIBUTION 
	//									DM_NODE_TYPE_CLUSTER      
	//									DM_NODE_TYPE_UNKNOWN      
	//
	//						  If you implement your own browser, you may define your own types
	//
	// DMNP_CONDITION_XML (BSTR) : the "condition" corresponding to the current node, in XML
	//
	// DMNP_RULE_XML (BSTR)	: the "rule" corresponding to the current node, in XML
	//
	// DMNP_PROBABILITY	(double) : the probability corresponding to the current node
	//
	// DMNP_MARGINAL_PROBABILITY (double) : the marginal probability corresponding to the current node
	//
	// DMNP_CAPTION	(BSTR) : the caption of the current node
	//
	// DMNP_SUPPORT	(double) : the support (number of cases) corresponding to the current node
	//
	// DMNP_MODEL_COLUMN_NAME (BSTR) : the "column name" corresponding to the current node
	//
	// DMNP_DESCRIPTION	(BSTR) : longer verion of the caption
	//
	// DMNP_SCORE (double) : the score of the node
	//
	// DMNP_SHORT_CAPTION (BSTR) : shorter version of the caption
	//
	//
	// For the string outputs, you need to allocate using the
	// appropriate methods of in_pContext.

	CComPtr<IDMVariantPtrHandler> spidmvariantptrhandler;
	HRESULT hr	= in_pContext->GetVariantHandler(&spidmvariantptrhandler);
	RETURN_ON_FAIL(hr);
	VARIANT variant;
	::VariantInit(&variant);

	switch (in_PropID)
	{
		case DMNP_TYPE:
		{
			// Cases handled by LRPHIERARCHY
			variant.lVal	= _lrphierarchy.UlType(_iIDNode);
			variant.vt		= VT_I4;

			break;
		}
		case DMNP_CONDITION_XML:
		case DMNP_RULE_XML:
		{
			// There's no condition or rule: the roots are leaves.
			// Return the empty string
			// ToDo: Eliminate call to ::SysAllocString using fixed interfaces
			variant.bstrVal = ::SysAllocString(L"");
			variant.vt		= VT_BSTR;

			break;
		}
		case DMNP_PROBABILITY:
		case DMNP_MARGINAL_PROBABILITY: 
		{
			// We have complete data
			variant.dblVal	= 1.0;
			variant.vt		= VT_R8;
			break;
		}
		case DMNP_CAPTION:
		case DMNP_MODEL_COLUMN_NAME:
		case DMNP_SHORT_CAPTION:
		case DMNP_DESCRIPTION:
		{
			// Use the name of the output attribute associated with this node
			ULONG iiAttribute	= _lrphierarchy.IiAttribute(_iIDNode);
			if (iiAttribute == (ULONG) -1)
			{
				return S_FALSE;
			}

			ULONG iAttribute	= _viAttributeOutput[iiAttribute];
			CComPtr<IDMStringHandler> spidmstringhandler;
			hr	= in_pContext->GetStringHandler(&spidmstringhandler);
			RETURN_ON_FAIL(hr);
			DMString* pdmstring;
			hr = spidmstringhandler->CreateNewStringHandle(&pdmstring);
			RETURN_ON_FAIL(hr);
			
			hr = _palgorithm->_spidmattributeset->GetAttributeDisplayName(
				in_pContext,
				iAttribute,
				(in_PropID == DMNP_SHORT_CAPTION) ? TRUE : FALSE,
				pdmstring);
			RETURN_ON_FAIL(hr);

			// Get the string into a buffer
			UINT cch = 100;
			WCHAR szName[101];
			hr = spidmstringhandler->CopyHandleToBuffer(pdmstring, szName, &cch);
			RETURN_ON_FAIL(hr);

			// ToDo: Eliminate call to ::SysAllocString using fixed interfaces
			variant.bstrVal = ::SysAllocString(szName);
			variant.vt		= VT_BSTR;
			hr = spidmstringhandler->DestroyStringHandle(pdmstring);
			RETURN_ON_FAIL(hr);

			break;
		}
		case DMNP_SCORE:
		{
			// Not defined for our model: return zero
			variant.dblVal	= 0.0;
			variant.vt		= VT_R8;

			break;
		}
		case DMNP_SUPPORT:
		{
			// Total number of cases
			variant.dblVal	= _palgorithm->_lrpmodel._cCase;
			variant.vt		= VT_R8;

			break;
		}
		default:
		{
			return S_FALSE;
		}
	}
	
	return spidmvariantptrhandler->CopyVariantToHandle(io_pValue, &variant);
}



STDMETHODIMP NAVIGATOR::GetNodeArrayProperty(
	/* [in] */ IDMContextServices*	in_pContext,
	/* [in] */ DM_NODE_PROP			in_PropID,
	/* [out] */ ULONG*				out_pulArraySize,
	/* [out] */ void**				out_prgArrayBuffer)
{
	IDMContextServices* pidmcontextservices = in_pContext;
	DM_NODE_PROP		dmnodeprop			= in_PropID;

	// Same as above, except for "non simple" properties:

	// DMNP_ATTRIBUTES (DMVariantPtr BSTR)	: a set of attribtues corresponding to the current node
	// DMNP_DISTRIBUTION (DM_ATTRIBUTE_STAT*) : the distribution corresponding to the 
	//											current node						
	
	ULONG iiAttribute	= _lrphierarchy.IiAttribute(_iIDNode);
	if (iiAttribute == (ULONG) -1)
	{
		// There's no output variable associated with the current node, which
		// means we're at the root of the browser hierarchy. We have nothing to
		// provide, regardless of the property requested.

		assert(!_bDMDimension);

		return S_FALSE;
	}

	ULONG iAttribute	= _viAttributeOutput[iiAttribute];
	if (in_PropID == DMNP_ATTRIBUTES)
	{
		// Return the name of the (single) relevant output variable, as a variant
		CComPtr<IDMStringHandler> spidmstringhandler;
		HRESULT hr	= pidmcontextservices->GetStringHandler(&spidmstringhandler);
		RETURN_ON_FAIL(hr);

		DMStringPtr pdmstring;
		hr = spidmstringhandler->CreateNewStringHandle(&pdmstring);
		RETURN_ON_FAIL(hr);

		hr = _palgorithm->_spidmattributeset->GetAttributeDisplayName(
			pidmcontextservices,
			iAttribute,
			FALSE,
			pdmstring);
		RETURN_ON_FAIL(hr);

		// Get the string into a buffer... assume that 100 characters is
		// sufficient

		UINT cch = 100;
		WCHAR szName[101];

		hr = spidmstringhandler->CopyHandleToBuffer(pdmstring, szName, &cch);
		RETURN_ON_FAIL(hr);

		// Create a vector of "DMVariantPtr" of length 1 
		// (because we have a single relevant attribute for this algorithm)
		// then fill its entries with 
		// IDMVariantPtrHandler::CreateNewVariantHandle()
		//

		CComPtr<IDMMemoryAllocator>	spidmmemoryallocator		= NULL;

		hr = in_pContext->GetVectorAlloc(&spidmmemoryallocator);
		RETURN_ON_FAIL(hr);
		
		ULONG	cAttributes = 1;
		
		DMVariantPtr*	rgVariantPtrs = NULL;
		hr = spidmmemoryallocator->Alloc(cAttributes * sizeof(DMVariantPtr),
										 (byte**) &rgVariantPtrs);
		RETURN_ON_FAIL(hr);
		memset( rgVariantPtrs, 0, cAttributes*sizeof(DMVariantPtr) );
		
		
		

		CComPtr<IDMVariantPtrHandler> spidmvarianthandler;
		hr	= pidmcontextservices->GetVariantHandler(&spidmvarianthandler);
		RETURN_ON_FAIL(hr);

		
		
		// Fill the vector entries with a new variant handle
		
		for( ULONG 	nIndex = 0; nIndex < cAttributes; nIndex ++ )
		{
			hr = spidmvarianthandler->CreateNewVariantHandle(&rgVariantPtrs[nIndex]);
			RETURN_ON_FAIL(hr);
		}


		// Just fill the first entry of the vector for this implementation
		VARIANT variant;
		::VariantInit(&variant);

		// ToDo: Eliminate call to ::SysAllocString using fixed interfaces
		variant.bstrVal = ::SysAllocString(szName);
		variant.vt		= VT_BSTR;

		hr = spidmvarianthandler->CopyVariantToHandle(rgVariantPtrs[0], &variant);
		RETURN_ON_FAIL(hr);

		hr = spidmstringhandler->DestroyStringHandle(pdmstring);
		RETURN_ON_FAIL(hr);

		*out_prgArrayBuffer = (void*) rgVariantPtrs;
		*out_pulArraySize	= cAttributes;
	}
	else if (in_PropID == DMNP_DISTRIBUTION)
	{ 
		// We specify a separate DM_ATTRIBUTE_STAT for 
		//
		//	(1) p(missing), the sample mean and the sample variance of the target
		//	(2) Each regressor coefficient or, if the _bDisplayCorrelation flag of
		//		the algorithm is set, each correlation coefficient.
		//  (3) The offset and the conditional variance. Because we are combining
		//		multiple pair-wise regression into the display, there is no single
		//		offset or conditional variance. For simplicity, we'll just use the
		//		sample mean of the target for the offset and the sample variance
		//		of the target for the conditional variance.
		//
		// The order of these array elements is important. Most of the fields need
		// not be specified; we'll initialize all fields to contain zero, and then
		// fill in the needed elements.
		//

 		LRPMODEL&	lrpmodel			= _palgorithm->_lrpmodel;
 		UINT		cRegressor			= (UINT) lrpmodel._vvlrparamView[iAttribute].size();
		UINT		cdmattributestat	= 2 + cRegressor * 2;

		// Allocate and initialize the output array
		DM_ATTRIBUTE_STAT*	rgdmattributestat = NULL;
        CComPtr<IDMMemoryAllocator>     spidmmemoryallocator;
        HRESULT hr = pidmcontextservices->GetVectorAlloc(&spidmmemoryallocator);
		RETURN_ON_FAIL(hr);
		hr = spidmmemoryallocator->Alloc(cdmattributestat * sizeof(DM_ATTRIBUTE_STAT),
										 (byte**) &rgdmattributestat);
		RETURN_ON_FAIL(hr);
	
		// Zero out memory
		memset(rgdmattributestat, 0, sizeof(DM_ATTRIBUTE_STAT) * cdmattributestat);

		// Leave NULL  all of the DMStringPtr members - they don't need to be populated

		// (1) p(missing), sample mean and sample variance of the target: 
		// We need to populate Attribute with the target attribute index. 
		// Then, as in a call to
		// IDMAlgorithm::Predict(), we create two states in rgStateStats: one 
		// corresponding to missing, and the other corresponding to present. The
		// present state contains, as its value, the sample mean of the target. The 
		// present state also contains the sample variance in dblVariance. Both
		// states need dblSupport and dblProbability to be filled in.... no other 
		// fields need to be set.
		ULONG	cCase	= lrpmodel._cCase;
		rgdmattributestat[0].Attribute	= iAttribute;
		rgdmattributestat[0].ulStates	= 2;
		hr = spidmmemoryallocator->Alloc(2 * sizeof(DM_STATE_STAT),
										 (byte**) &rgdmattributestat[0].rgStateStats);
		RETURN_ON_FAIL(hr);

		// Zero out state memory
		memset(rgdmattributestat[0].rgStateStats, 0, 2 * sizeof(DM_STATE_STAT));

		// Missing state
		rgdmattributestat[0].rgStateStats[0].Value = 
			::DmstatevalueContinuous(dblMissing);
		
		rgdmattributestat[0].rgStateStats[0].dblSupport		= 0.0;
		rgdmattributestat[0].rgStateStats[0].dblProbability	= 0.0;
		
		// Non-missing state
		rgdmattributestat[0].rgStateStats[1].Value = 
			::DmstatevalueContinuous(lrpmodel._vdblSampleMean[iAttribute]);
	
		DBL dblSD = lrpmodel._vdblSampleSD[iAttribute];
		rgdmattributestat[0].rgStateStats[1].dblVariance	= dblSD * dblSD;
		rgdmattributestat[0].rgStateStats[1].dblSupport		= cCase;
		rgdmattributestat[0].rgStateStats[1].dblProbability	= 1.0;
		
		// (2) Regression coefficients: we set the iAttribute field to
		// correspond to the regressor, and we create a single state that
		// specifies the value of the coefficient and the sample variance
		// of the regressor. If the algorithm's _bDisplayCorrelation is true,
		// we replace the linear coefficients by the correlation: the correlation
		// is simply the linear coefficient times the ratio of the regressor SD
		// and the target SD.

		for (UINT iRegressor = 0; iRegressor < cRegressor; iRegressor++)
		{
			DM_ATTRIBUTE_STAT*	pattributestat = &rgdmattributestat[1 + iRegressor*2];
			LRPARAM& lrparam = lrpmodel._vvlrparamView[iAttribute][iRegressor];
			pattributestat->Attribute	= lrparam._iAttributeRegressor;
			pattributestat->ulStates	= 1;
		
			hr = spidmmemoryallocator->Alloc(sizeof(DM_STATE_STAT),
											 (byte**) &pattributestat->rgStateStats);
		
			RETURN_ON_FAIL(hr);
			memset(pattributestat->rgStateStats, 0, sizeof(DM_STATE_STAT));

			// Set the coefficient and sample variance
			if (_palgorithm->_bDisplayCorrelation)
			{
				DBL dblCorrelation = 
					lrparam._dblSlope 
					* lrpmodel._vdblSampleSD[lrparam._iAttributeRegressor]
					/ lrpmodel._vdblSampleSD[iAttribute];

				pattributestat->rgStateStats[0].Value = 
					::DmstatevalueContinuous(dblCorrelation);
			}
			else
			{
				pattributestat->rgStateStats[0].Value = 
					::DmstatevalueContinuous(lrparam._dblSlope);
			}
	
			DBL dblSD = lrpmodel._vdblSampleSD[lrparam._iAttributeRegressor];
			pattributestat->rgStateStats[0].dblVariance = dblSD * dblSD;

			// Set the STATISTICS (type 9) information. This is due to a 
			// bug in the viewer requiring the conditional mean and varience
			// of a node. 
			pattributestat++;
			pattributestat->Attribute	= lrparam._iAttributeRegressor;
			pattributestat->ulStates	= 1;
			hr = spidmmemoryallocator->Alloc(sizeof(DM_STATE_STAT),
											 (byte**) &pattributestat->rgStateStats);
			RETURN_ON_FAIL(hr);
			memset(pattributestat->rgStateStats, 0, sizeof(DM_STATE_STAT));

			pattributestat->rgStateStats[0].Value = 
			::DmstatevalueContinuous(lrpmodel._vdblSampleMean[lrparam._iAttributeRegressor]);

			dblSD = lrpmodel._vdblSampleSD[lrparam._iAttributeRegressor];
			pattributestat->rgStateStats[0].dblVariance = dblSD * dblSD;
			pattributestat->rgStateStats[0].dblSupport  = -2.0;	// hacky way of indicating it's a contidional mean

		}
	
		// (3) the offset and the conditional variance: we use DM_UNSPECIFIED
		// for the iAttribute, and then create a single state whose value
		// is zero and whose dblVariance is the sample variance.
		DM_ATTRIBUTE_STAT&	dmattributestat = rgdmattributestat[1 + cRegressor * 2];
		dmattributestat.Attribute	= DM_UNSPECIFIED;
		dmattributestat.ulStates	= 1;
		
		hr = spidmmemoryallocator->Alloc(sizeof(DM_STATE_STAT),
									     (byte**) &dmattributestat.rgStateStats);
		RETURN_ON_FAIL(hr);

		// Set the coefficient and sample variance
		memset(dmattributestat.rgStateStats, 0, sizeof(DM_STATE_STAT));
		dmattributestat.rgStateStats[0].Value = ::DmstatevalueContinuous(0.0);
		dmattributestat.rgStateStats[0].dblVariance = dblSD * dblSD;
		*out_pulArraySize	= cdmattributestat;
		*out_prgArrayBuffer = rgdmattributestat;
	}

	return S_OK;
}


STDMETHODIMP NAVIGATOR::GetNodeUniqueName(
	/* [in] */ IDMContextServices*	in_pContext,
	/* [out] */ DMString*			io_pstrUniqueName)
{
	// Return the unique string name of the current node

	return GetUniqueNameFromNodeID( in_pContext,
									_iIDNode,
									io_pstrUniqueName );
}

STDMETHODIMP NAVIGATOR::GetNodeIDFromUniqueName(
	/* [in] */ IDMContextServices*	in_pContext,
    /* [in] */ DMString*			in_pstrUniqueName,
    /* [out] */ DM_DAGNodeID*		out_pNodeID)
{
	// Translate from unique string name to node id
	CComPtr<IDMStringHandler> spidmstringhandler;
	HRESULT hr = in_pContext->GetStringHandler(&spidmstringhandler);
	RETURN_ON_FAIL(hr);
	const WCHAR* szName = NULL;
	UINT cch;
	hr = spidmstringhandler->GetConstStringFromHandle(in_pstrUniqueName,
													  &szName,
													  &cch);
	RETURN_ON_FAIL(hr);
	*out_pNodeID = _lrphierarchy.IDFromSz(szName);

	return S_OK;
}

STDMETHODIMP NAVIGATOR::GetUniqueNameFromNodeID( 
    /* [in] */ IDMContextServices*	in_pContext,
    /* [in] */ DM_DAGNodeID			in_NodeID,
    /* [out] */ DMString*			out_pstrUniqueName)
{
	// Translate from node id to unique string name
	WCHAR szName[21]; // Enough for models with 10^{20} nodes.
	_lrphierarchy.GetSzFromID(szName, (ULONG)in_NodeID, 20);
	CComPtr<IDMStringHandler> spidmstringhandler;
	HRESULT hr = in_pContext->GetStringHandler(&spidmstringhandler);
	RETURN_ON_FAIL(hr);
	UINT cch = (UINT)wcslen(szName);
	return spidmstringhandler->CopyBufferToHandle(out_pstrUniqueName, szName, cch);
}

